﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Circa.Tools;
using Configuration = Circa.Configuration;

namespace Circa.Protocol.Irc {

    public class GenericProtocolEngine: ProtocolEngine {
        
        private Connection _connection;
        public override Connection Connection {
            get {
                return _connection;
            }
            set {
                if ( null != _connection ) {
                    _connection.Connected -= ConnectedHandler;
                    _connection.ConnectError -= ConnectErrorHandler;
                    _connection.Line -= LineHandler;
                    _connection.ReadError -= ReadErrorHandler;
                    _connection.WriteError -= WriteErrorHandler;
                    _connection.Dispose( );
                    _connection = null;
                }

                _connection = value;
                _connection.Connected += ConnectedHandler;
                _connection.ConnectError += ConnectErrorHandler;
                _connection.Line += LineHandler;
                _connection.ReadError += ReadErrorHandler;
                _connection.WriteError += WriteErrorHandler;
            }
        }
        public override bool IsRegistered { get; set; }

        public Server Server { get; set; }
        public Network Network {
            get {
                return Server.Network;
            }
        }
        public SelfUser Self {
            get {
                return Server.Self;
            }
        }

        private SafeDictionary<string, ITargetable> targets = new SafeDictionary<string, ITargetable>( );
        private AutoSafeObjectDictionary<string, List<Action<IrcMessage>>> verbs = new AutoSafeObjectDictionary<string, List<Action<IrcMessage>>>( );

        public GenericProtocolEngine( ) {
            verbs[ "001" ].Add( RplWelcome );
            verbs[ "002" ].Add( RplYourHost );
            verbs[ "003" ].Add( RplCreated );
            verbs[ "004" ].Add( RplMyInfo );
            verbs[ "CAP" ].Add( VerbCAP );
            verbs[ "ERROR" ].Add( VerbERROR );
            verbs[ "KILL" ].Add( VerbKILL );
            verbs[ "NOTICE" ].Add( VerbNOTICE );
            verbs[ "PING" ].Add( VerbPING );
            verbs[ "PRIVMSG" ].Add( VerbPRIVMSG );
            verbs[ "QUIT" ].Add( VerbQUIT );

            // the following verbs need to be moved to a more concrete implementation
            /*
            verbs[ "005" ].Add( RplISupport );
            verbs[ "372" ].Add( RplMotd );
            verbs[ "375" ].Add( RplMotdStart );
            verbs[ "376" ].Add( RplEndOfMotd );
            //verbs[ "401" ].Add( ErrNoSuchNick );
            verbs[ "422" ].Add( ErrNoMotd );
            //verbs[ "431" ].Add( ErrNoNicknameGiven );
            //verbs[ "432" ].Add( ErrErroneousNickname );
            verbs[ "MODE" ].Add( VerbMODE );
            */
        }

        //
        // Outbound events from IProtocolEngine
        //

        /// <summary>
        /// This event is fired as soon as the TCP connection to the server enters the CONNECTED state.
        /// </summary>
        public override event EventHandler<EventArgs> ConnectionEstablished;
        protected virtual void OnConnectionEstablished( EventArgs e ) {
            var handler = ConnectionEstablished;
            if ( null != handler )
                handler( this, e );
        }

        /// <summary>
        /// This event is fired once NICK, USER, maybe PASS, and usually PONG have all been exchanged, and we see the RPL_MYINFO (004) numeric.
        /// </summary>
        public override event EventHandler<EventArgs> Registered;
        protected virtual void OnRegistered( EventArgs e ) {
            var handler = Registered;
            if ( null != handler )
                handler( this, e );
        }

        //
        // Public methods for IProtocolEngine
        //

        public override bool RenameTarget( string oldName, string newName ) {
            if ( null == targets )
                return false;

            ITargetable target = targets[ oldName ];
            if ( null == target )
                return false;

            targets.Remove( oldName );
            targets.Add( newName, target );
            return true;
        }

        //
        // Public methods for IProtocolPerformer
        //

        public override void Join( Channel channel ) {
            // If the Channel object already has a Server attached, and that server is not us, fail
            if ( null != channel.Server && channel.Server != Server ) {
                throw new InvalidOperationException( string.Format( "GenericProtocolEngine.Join: channel {0} already has a different Server ({1}) attached?", channel.Name, channel.Server.Name ) );
            }

            // right network?
            if ( null != channel.Network && Server.Network != channel.Network ) {
                throw new InvalidOperationException( string.Format( "GenericProtocolEngine.Join: channel {0} has wrong Network {1} (should be {2})", channel.Name, channel.Network.Name, Server.Network.Name ) );
            }

            // if the channel is already registered as a target, we've already done a server JOIN for it
            // XXX this is possibly a sheaf of invalid states?
            ITargetable tmp = targets[ channel.Name ];
            if ( null != tmp ) {
                if ( tmp is Channel ) {
                    if ( Server == ( (Channel) tmp ).Server ) {
                        // already registered, so we're done
                        return;
                    } else {
                        // a Channel that does not belong to us is in our targets array??
                        throw new InvalidOperationException( string.Format( "GenericProtocolEngine.Join: Channel {0} is already a registered target, but not on this server?", channel.Name ) );
                    }
                } else {
                    // an object of some other type is registered with the channel's name??
                    throw new InvalidOperationException( string.Format( "GenericProtocolEngine.Join: Channel {0} is already a registered target, but object's type is {1}?", channel.Name, tmp.GetType( ).ToString( ) ) );
                }
            } else {
                targets.Add( channel.Name, channel );
            }
            channel.Server = Server;
            channel.Network = Server.Network;

            if ( string.IsNullOrEmpty( channel.Key ) ) {
                Connection.Send( "JOIN {0}", channel.Name );
            } else {
                Connection.Send( "JOIN {0} {1}", channel.Name, channel.Key );
            }
        }

        public override void Nick( string newNick ) {
            Connection.Send( "NICK {0}", newNick );
        }

        public override void Pong( ) {
            Connection.Send( "PONG" );
        }

        public override void Pong( string arg ) {
            Connection.Send( "PONG :{0}", arg );
        }

        public override void User( string userName, string userHostName, string serverHostName, string realName ) {
            Connection.Send( "USER {0} {1} {2} :{3}", userName, userHostName, serverHostName, realName );
        }

        public override void Who( string target ) {
            Connection.Send( "WHO {0}", target );
        }

        //======================================================================
        // Implementation
        //======================================================================

        //
        // Events from Connection object
        //

        void LineHandler( object sender, LineEventArgs e ) {
            IrcMessage m = e.IrcMessage;
            m.Server = Server;

            // see if we can fill in the Origin property
            if ( null != m.OriginName ) {
                if ( m.OriginName.IndexOf( '.' ) > -1 ) {
                    // name represents a server
                } else {
                    m.Origin = targets[ m.OriginName ];
                }
            }

            // first, try to dispatch the message to some other object
            if ( null != m.TargetName ) {
                m.Target = targets[ m.TargetName ];
            }

            if ( null != m.Target && m.Target.Dispatch( m ) ) {
                return;
            }

            // failing that, see if it's something we're going to handle ourselves
            if ( Dispatch( "GenericProtocolEngine.LineHandler", verbs[ m.Verb ], m ) ) {
                return;
            }

            // and if not, complain before we throw it on the floor
            // XXX there ought to be some sort of "last chance" hook this gets routed to instead
            Debug.Print( "GenericProtocolEngine.LineHandler: Unhandled line: Origin={0} Verb={1} Target={2} Args={{{3}}}", m.OriginName, m.Verb, m.TargetName, string.Join( "}{", m.Args.ToArray( ) ) );
            throw new NotImplementedException( );
        }

        void WriteErrorHandler( object sender, NetworkErrorEventArgs e ) {
            Debug.Print( "GenericProtocolEngine.WriteErrorHandler: {0}", e.Exception.ToString( ) );
            throw new NotImplementedException( );
        }

        void ReadErrorHandler( object sender, NetworkErrorEventArgs e ) {
            Debug.Print( "GenericProtocolEngine.ReadErrorHandler: {0}", e.Exception.ToString( ) );
            throw new NotImplementedException( );
        }

        void ConnectErrorHandler( object sender, NetworkErrorEventArgs e ) {
            Debug.Print( "GenericProtocolEngine.ConnectErrorHandler: {0}", e.Exception.ToString( ) );
            throw new NotImplementedException( );
        }

        void ConnectedHandler( object sender, EventArgs e ) {
            OnConnectionEstablished( EventArgs.Empty );

            // HOOK

            User( Self.UserName, Self.HostName, Connection.HostName, Self.RealName );
            Nick( Self.Name );
        }

        //
        // IRC protocol events
        //

        void RplWelcome( IrcMessage m ) {
            // TODO end of arg may contain our nick!user@host
            Debug.Print( "GenericProtocolEngine.001: {0}", m.Args[ 0 ] );
        }

        void RplYourHost( IrcMessage m ) {
            Debug.Print( "GenericProtocolEngine.002: {0}", m.Args[ 0 ] );
        }

        void RplCreated( IrcMessage m ) {
            Debug.Print( "GenericProtocolEngine.003: {0}", m.Args[ 0 ] );
        }

        void RplMyInfo( IrcMessage m ) {
            Debug.Print( "GenericProtocolEngine.004: serverhost/{0}/ serverversion/{1}/ userflags/{2}/ channelflags/{3}/", m.Args[ 0 ], m.Args[ 1 ], m.Args[ 2 ], m.Args[ 3 ] );

            IsRegistered = true;
            OnRegistered( EventArgs.Empty );
        }

        //
        // IRC protocol verbs
        //

        void VerbCAP( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        void VerbERROR( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        void VerbKILL( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        void VerbNOTICE( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        void VerbPING( IrcMessage m ) {
            if ( m.Args.Count > 0 )
                Pong( string.Join( " ", m.Args.ToArray( ) ) );
            else
                Pong( );
        }

        void VerbPRIVMSG( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        void VerbQUIT( IrcMessage m ) {
            throw new NotImplementedException( );
        }

        //
        // 
        //

        bool Dispatch( string contextName, List<Action<IrcMessage>> handlers, IrcMessage m ) {
            bool fHandled = false;

            foreach ( var action in handlers ) {
                try {
                    action( m );
                    fHandled = true;
                }
                catch ( Exception e ) {
                    Debug.Print( "GenericProtocolEngine.Dispatch {0}: exception occurred dispatching {1}: {2}", contextName, m.Verb, e );
#if DEBUG
                    throw;
#endif
                }
            }

            return fHandled;
        }

    }

}
